#include "SocketConcurrentDemo.h"
#include <iostream>
#include "pthread.h"

using namespace std;

struct SocketInfo {
    sockaddr_in addr;
    SOCKET socket;
} clientSocketInfoArray[100];

void socketConcurrentTcpServer()
{
    cout << "案例二：最简多线程处理并发服务器（流式tcp）" << endl;

#ifdef _WIN32  
    // windows 下需要装载网络访问库
    WSADATA lpWSAData;
    WSAStartup(MAKEWORD(2, 2), &lpWSAData);
#endif

    // 1. 创建 socket
    // SOCK_DGRAM 报式传输，不需要监听可随时发送随时接受，但是报式不稳定，传输效率很高。
    // SOCK_STREAM 流式传输，需要监听，并且会完成三步握手和四步回收，流式传输效率不如报式，但相对稳定。
    // 流式默认使用 tcp 协议，报式默认使用 udp 协议。填写 0 则表示使用默认协议。这里除了 tcp 协议和 udp 协议还有别的协议，如何关联还需要其他研究。
    // 但目前如果使用 tcp 协议来创建报式传输模式会创建失败，同理用 udp 来创建流式传输模式也会失败。
    // 但在一些资料上显示报式模式和流式模式与具体协议无关，udp 也可以用于流式，这显然有些不正确，因为本人亲测通显示指定 IPPROTO_UDP 来创建 SOCK_STREAM。或者 IPPROTO_TCP 来创建 SOCK_DGRAM 都报错失败了。
    // 错误示例：socket(AF_INET, SOCK_STREAM, IPPROTO_UDP) 或 socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP)
    // 正确示例：socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) 或 socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
    SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if(serverSocket == -1) {
        cerr << "创建失败！！" << endl;
        return;
    }

    // 2. bind
    sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    // INADDR_NONE：        表示无效的IP地址，用于表示错误或无效的地址。
    // INADDR_BROADCAST：   表示广播地址，用于向网络中的所有主机发送数据。
    // INADDR_LOOPBACK：    表示回环地址，用于将数据发送到本地主机上的回环接口。
    // INADDR_ANY：         表示通配地址，用于指示可以接受来自任何网络接口的数据。
    serverAddr.sin_addr.s_addr = INADDR_ANY;  // 使用 INADDR_ANY 等效于 inet_addr("127.0.0.1"); 在绑定时会自动寻找网卡的IP地址予以绑定，INADDR_ANY 值为 0 即，0.0.0.0 IP
    serverAddr.sin_port = htons(9999);        // 端口使用网络字节序，即大端字节序，函数名称记忆理解：h -> host（主机）; n -> net(网络); s-> short(短整型)。 主机to网络的短整型(htons)
    int bindRet = ::bind(serverSocket, (sockaddr*) &serverAddr, sizeof(serverAddr));
    if(bindRet == -1) {
        cerr << "绑定端口失败！！！" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 3. listen
    int listenRet = listen(serverSocket, 100);   // 要监听的 socket，一次可以获取多少个终端链接
    if(listenRet == -1) {
        cerr << "监听失败！！！" << endl;
        compatCloseSocket(serverSocket);
        return;
    }

    // 4. 阻塞等待链接
    /**
     * 简单做法如下： 为了比较区别保留这个注释
     * 
     *  sockaddr_in clientAddr;
     *  // unsigned int clientAddrLength = sizeof(clientAddr);
     *  addrlen clientAddrLength = sizeof(clientAddr);
     *  SOCKET clientSocket = accept(serverSocket, (sockaddr*) &clientAddr, &clientAddrLength);
     *  if(clientSocket == -1) {
     *      cerr << "接受客户端异常！！！" << endl;
     *      compatCloseSocket(serverSocket);
     *      return;
     *  }
     *  // 输出接入链接的客户端信息
     *  // char ip[24] = { 0 };
     *  // inet_ntop(AF_INET, &clientAddr.sin_addr.s_addr, ip, sizeof(ip)); // inet_ntop 为 linux 下的接口方法，windows 并不支持。 
     * 
     *  // inet_ntoa 函数 windows和linux都支持，但只能作用于IPV4，无法处理IPV6
     *  cout << "客户端IP：" << inet_ntoa(clientAddr.sin_addr) << "；端口：" << ntohs(clientAddr.sin_port) << "已连接" << endl;
     */

    // 处理并发的解决方案，如下: 

    // * 初始化客户端Socket数组
    const int length = sizeof(clientSocketInfoArray) / sizeof(clientSocketInfoArray[0]);
    for(int index = 0; index < length; ++index)
    {
        memset(&clientSocketInfoArray[index], 0, sizeof(clientSocketInfoArray[index]));
        clientSocketInfoArray[index].socket = -1;
    }

    // * 定义接受客户端的结构体
    addrlen clientAddrLength = sizeof(sockaddr_in);
    SocketInfo* clientSocketInfo;
    while(true) {
        // * 选中闲置容器用于存储连接的客户端
        for(int index = 0; index < length; ++index) {
            if(clientSocketInfoArray[index].socket == -1) {
                clientSocketInfo = &clientSocketInfoArray[index];
                break;
            }
        }
        
        // * 保存连接客户端信息
        clientSocketInfo->socket = accept(serverSocket, (sockaddr*) &clientSocketInfo->addr, &clientAddrLength);
        if(clientSocketInfo->socket == -1) {
            cerr << "接受客户端异常！！！" << endl;
            // compatCloseSocket(serverSocket);
            continue;
        }

        // 打印一下连接过来的客户端信息
        cout << "客户端IP：" << inet_ntoa(clientSocketInfo->addr.sin_addr) << "；端口：" << ntohs(clientSocketInfo->addr.sin_port) << "已连接" << endl;

        // * 创建一个新线程用于服务连接过来的客户端，读取上报数据及后续操作。
        pthread_t acceptThreadId;
        // 申明一下方法结构
        void* readClientSocket(void*);
        // pthread 是一个 C 代码库，在 C++ 中早期没有自己的标准多线程库也是使用 C 代码的线程库，在 C++ 11 标准以后 C++ 提出了自己的标准线程库 thread 可以 #include<thread> 引用。
        // 在这里依旧使用 C 的线程库是为了学习和理解 C++ 中各种使用情况，之后会写一些 C++ 的多线程操作案例。
        // C 的 pthread 库需要使用 pthread_create 来创建线程，他的参数分别为：
        //  1. 线程 ID，是一个 pthread_t 类型的出参用于保存线程的 id，一般简写为 tid。我习惯了 java 命名规则即代码自释，所以允许的情况下不会使用简写。
        //  2. 代表创建出的子线程的线程属性：线程的优先级、守护线程、线程组和处理未捕获异常的处理器。使用空指针表示默认属性。
        //     * 线程优先级：在Java程序设计语言中，每一个线程都有一个优先级。默认情况下，一个线程继承它的父线程的优先级。
        //       可以将优先级设置为MIN_PRIORITY（在Thread类中定义为1）与MAX_PRIORITY（定义为10）之间的任何值。
        //     * 守护线程：通过调用线程的setDaemon(true)方法，可以将线程转换为守护线程（daemon thread）。守护线程的唯一作用是为其他线程提供服务。
        //     * 未捕获异常处理器：原因是线程的run方法不能抛出任何被检测的异常，但是，不被检测的异常会导致线程终止。
        //     * 此外，线程属性还包括作用域（scope）、栈尺寸（stack size）、栈地址（stack address）、分离的状态（detached state）、调度策略和参数（scheduling policy and parameters）等。
        //  3. 新线程的线程函数开始地址，即为新线程要执行的函数。这里和 java 的 Running 接口一个意思，但是这里传入的是一个函数指针。
        //     其函数指针类型为 viod* (*fnc)(void*) 即：接受一个任意类型指针参数，并返回一个任意类型参数的函数；所以如果需要返回或接受多个参数可以使用自定义结构体封装。
        //  4. 传递给上面要被放在新线程中执行的函数的实参。
        pthread_create(&acceptThreadId, nullptr, readClientSocket, clientSocketInfo);
        
        // 线程执行完成需要回收线程资源，如果不调用 pthread_join 或者 pthread_detach 函数，线程依旧会正常开辟并执行，但可能导致线程无法回收内存资源以及其他问题。
        //  * pthread_join 函数，主动回收一个线程资源。如果一个线程没有调用 pthread_detach 转入自动回收，就需要我们手动 pthread_join 回收。
        //  * pthread_detach 函数，将线程从当前线程分离，并转入自动维护，线程在执行结束退出后自动会后资源。
        // 注意：如果需要从某个线程接受方法执行后的返回值，就必须使用 pthread_join，因为 pthread_detach 转自动管理后，我们就无法拿到返回值了。当然多线程数据操作我们还可以使用共享数据来完成。
        // pthread_join(acceptThreadId, nullptr);  // 这个函数会阻塞当前线程，等待子线程执行完毕后在回归合并到主线程继续执行。它接受一个线程ID参数表示要回收的线程，以及一个任意类型的指针接受线程函数返回值
        pthread_detach(acceptThreadId);         // 这个函数会将执行线程分离成为守护线程。线程并不会阻塞在这里。
    }

    // 6. 关闭链接
    compatCloseSocket(serverSocket);
    
#ifdef _WIN32  
    // windows 平台下结束了还需要移除网络库的装载
    WSACleanup();
#endif
}

void* readClientSocket(void* socketInfo)
{
    SocketInfo* clientSocketInfo = (SocketInfo*) socketInfo;

    // 5. 读取数据
    char readBuff[1024];
    while(true) {
        // memset(readBuff, 0, sizeof(readBuff));
        // int readRet = read(clientSocketInfo->socket, readBuff, sizeof(readBuff));  // linux 下存在的方法。
        int readRet = recv(clientSocketInfo->socket, readBuff, 1024, 0);            // linux 和 windows 都支持
        if(readRet > 0) {
            // 成功读取数据, 返回值表示为接受数据的长度
            cout << "收到客户端数据: " << readBuff << endl;
            // write(clientSocketInfo->socket, readBuff, readRet);                  // linux 下存在的方法。
            send(clientSocketInfo->socket, readBuff, readRet, 0);                   // linux 和 windows 都支持
        } else if (readRet == 0) {
            // 客户端链接已断开
            cout << "客户端断开链接！" << endl;
            break;
        } else {
            // 读取失败，异常
            cout << "读取失败异常: " << endl;
            break;
        }
    }

    // 6. 关闭链接
    compatCloseSocket(clientSocketInfo->socket);
    clientSocketInfo->socket = -1;

    return nullptr;
}
